GBSV - Mini-Challenge 2¶

Falls keine Code-Quelle angegeben wurde, wurde der Code zusammen mit ChatGPT und Copilot erzeugt.

Grössere Funktionen habe ich im Python File helper.py programmiert, damit das Notebook übersichtlich bleibt.

In [ ]:
# General libraries
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# Libraries related to image processing
from PIL import Image
import cv2
from skimage import io, color, filters, measure, morphology, util
from scipy import ndimage as ndi

# Libraries related to audio/signal processing
import IPython.display as ipd
import scipy as sp
from scipy.signal import correlate

from helper import *

1. Mustersuche in Bild und Signal (LE3)¶

1.1. Korrelation in Signalen¶

1.1.1 Signal mit wiederkehrende Muster¶

Ich werde hier zwei Signale erzeugen. Eine sehr simple Sinuswelle und viele kombinierte Sinuswellen mit einem Muster.

1.1.1.1 Einfache Sinuswelle¶

Dieses Signal ist eine einfache Sinuswelle mit einer Frequenz von 110 Hz, erstellt für eine Dauer von 1 Sekunde. Die Frequenz von 110 Hz entspricht dem musikalischen Ton A2. Bei einer Samplingrate von 44'100 Hz wurde diese Sinuswelle generiert, um einen klaren und kontinuierlichen Ton zu erzeugen.

In [ ]:
# Frequency of the sine wave in Hertz
frequency = 110  

# Time axis for 4 seconds
t_sine = np.linspace(0, 1, 44100)

# Generating the sine wave
sine_wave = np.sin(2 * np.pi * frequency * t_sine)

print("Einfache Sinuswelle")
ipd.display(ipd.Audio(data=sine_wave, rate=44100))

plot_signal(sine_wave[:3000], t_sine[:3000], title='Einfache Sinuswelle bei 110Hz', xlabel='Zeit (Sekunden)', ylabel='Amplitude')
Einfache Sinuswelle
Your browser does not support the audio element.

In der Visualisierung werden nur die ersten 3000 Samples dargestellt, um die regelmässigen und glatten Schwingungen der Welle deutlich zu machen, die die Änderungen in der Amplitude über die Zeit zeigen.

1.1.1.2 Komplexes Signal mit Muster¶

Dieses Signal ist eine komplexere und dynamischere Kombination von Sinuswellen, die einen C-Dur-Akkord in drei verschiedenen Oktaven darstellen (C3, E3, G3; C4, E4, G4; C5, E5, G5). Das Signal wird zunächst durch Summierung dieser Sinuswellen gebildet, was zu einer reichen und harmonischen Mischung führt. Um rhythmische Variationen und ein Gefühl der Bewegung hinzuzufügen, wird das Signal dann in der Amplitude moduliert. Diese Amplitudenmodulation ist nicht konstant, sondern variiert mit der Zeit, da sie selbst von einer langsameren Sinuswelle moduliert wird. Diese Modulation erzeugt einen pulsierenden Effekt.

In [ ]:
# Time axis for 5 seconds
t = np.linspace(0, 4, 44100 * 4)

# Frequencies for a C major chord: C, E, G in different octaves
frequencies = [261.63, 329.63, 392.00,  # C4, E4, G4
               523.25, 659.25, 783.99,  # C5, E5, G5 (One octave higher)
               130.81, 164.81, 196.00]  # C3, E3, G3 (One octave lower)

# Generating a more complex combination of sine waves
signal = np.sum([np.sin(2 * np.pi * f * t) for f in frequencies], axis=0)

# Amplitude modulation with variable rate
modulation_rate = 1 + np.sin(2 * np.pi * 0.25 * t)  # Modulation rate varies over time
signal *= 0.5 * (1 + np.sin(2 * np.pi * modulation_rate * t))

print("Komplexes Signal")
ipd.display(ipd.Audio(data=signal, rate=44100))

plot_signal(signal, t, title='Complex Signal', xlabel='Time (seconds)', ylabel='Amplitude')
Komplexes Signal
Your browser does not support the audio element.

1.1.2 Auto-Korrelation auf wiederkehrende Muster¶

Als nächstes werde ich die Auto-Korrelationsanalyse für beide Signale durchführen. Die Auto-Korrelation misst, wie gut das Signal zu einer verschobenen Version von sich selbst passt und dient dazu, periodische Eigenschaften (wiederkehrende Muster) innerhalb des Signals zu identifizieren.

Guide to Autocorrelation

Autocorrelation of Time Series

In [ ]:
df_sine = pd.DataFrame(sine_wave, index=t_sine, columns=['Amplitude'])
df_sine.index.name = 'Time'

lags = [100, 200, 400, 1000]

for lag in lags:
    auto_correlation(df_sine, lag, "Einfache Sinuswelle")

Die vier Diagramme zeigen die Auto-Korrelation der einfachen Sinuswelle für vier verschiedene Lags (100, 200, 400 und 1000).

  • 100 Lags: Mit einer geringen Anzahl von Lags zeigt das Auto-Korrelogramm eine schnelle Abfolge von hohen Korrelationswerten. Dies deutet darauf hin, dass die Periode des Signals sehr kurz ist, wodurch es sich schnell wiederholt.

  • 200 Lags: Die Verdoppelung der Lags führt zu einer Ausweitung des Korrelogramms, wobei die Korrelationswerte in einem breiteren Bereich verteilt sind. Die Periode des Signals bleibt erkennbar und die Korrelationswerte fallen gleichmässig ab.

  • 400 Lags: Hier wird die Periodizität des Signals noch deutlicher. Die Korrelationswerte bilden eine wellenförmige Kurve, die die periodische Natur des Sinussignals über einen längeren Zeitraum widerspiegelt.

  • 1000 Lags: Bei einer hohen Anzahl von Lags zeigt das Auto-Korrelogramm eine deutlich wellenförmige Struktur, die über längere Zeit konsistent bleibt. Die hohen und niedrigen Korrelationswerte wechseln sich regelmässig ab, was ein klares Zeichen für ein stark periodisches Signal ist.

In [ ]:
df_complex = pd.DataFrame(signal, index=t, columns=['Amplitude'])
df_complex.index.name = 'Time'

lags = [100, 500, 1000]

for lag in lags:
    auto_correlation(df_complex, lag, "komplexes Signal")
  • 100 Lags: Der Auto-Korrelogramm zeigt eine rasche Abnahme der Korrelation, gefolgt von kleineren Fluktuationen, die sich mit der Zeit stabilisieren. Dies deutet darauf hin, dass das Signal eine Art von regelmässigem Muster oder Rhythmus aufweist, aber die Komplexität des Signals verursacht Variationen in der Korrelation über kürzere Lags.

  • 500 Lags: Mit einer erhöhten Anzahl von Lags zeigt das Korrelogramm eine deutliche periodische Fluktuation, die die komplexe Natur des Signals widerspiegelt. Es gibt ausgeprägte Spitzen und Täler, die darauf hindeuten, dass das Signal aus mehreren überlagerten Frequenzen besteht, die ihre eigene Periodizität haben.

  • 1000 Lags: Bei der höchsten Anzahl von Lags wird die komplexe Struktur des Signals noch offensichtlicher. Die Korrelationswerte zeigen eine ausgeprägte wellenförmige Bewegung, was auf eine Überlagerung verschiedener periodischer Muster im Signal hinweist. Die Peaks im Auto-Korrelogramm sind weniger regelmässig als bei einer einfachen Sinuswelle, was auf die grössere Komplexität des Signals hindeutet.

1.3 Kann die Periodizität deines Musters via Auto-Korrelogramm sichtbar gemacht werden?¶

Ja, die Periodizität des Musters kann durch das Auto-Korrelogramm sichtbar gemacht werden. Die Experimente mit verschiedenen Lags haben gezeigt, dass die Auto-Korrelation eine wiederkehrende Struktur innerhalb des komplexen Signals offenlegen kann. Bei den geringeren Lags (100 und 500) sind die wiederkehrenden Muster durch regelmässige Fluktuationen im Korrelogramm erkennbar, obwohl die komplexe Natur des Signals zu Variationen führt. Obwohl die Periodizität in einem komplexen Signal nicht so ausgeprägt ist wie in einer einfachen Sinuswelle, zeigt das Auto-Korrelogramm dennoch klar wiederkehrende Muster und bestätigt die periodische Natur des Signals.

1.4 Diskutiere deine Methoden- und Parameterwahl sowie die Resultate in ca. 150 Wörtern.¶

Die Auswahl der Methoden für die Analyse der Periodizität komplexer Signale mittels Auto-Korrelation erwies sich als effektiv. Die Parameter wurden so gewählt, dass sie eine Kombination aus verschiedenen Frequenzen und Amplitudenmodulation darstellen, was ein realistisches Szenario für ein komplexes Signal simuliert. Die Verwendung unterschiedlicher Lags ermöglichte es, die Struktur des Signals auf verschiedenen Skalen zu untersuchen. Die Resultate zeigen, dass trotz der komplexen Zusammensetzung des Signals die Auto-Korrelogramme wiederkehrende Muster enthüllen konnten. Bei geringeren Lags war die Periodizität des Signals durch schnelle Abnahmen und Schwankungen der Korrelation deutlich, während bei höheren Lags die Überlagerung verschiedener periodischer Komponenten sichtbar wurde. Diese Erkenntnisse bestätigen, dass die Auto-Korrelation ein robustes Werkzeug zur Identifizierung periodischer Eigenschaften in komplexen Signalen ist.

Nun schneiden wir ein Stück des komplexen Signals aus und versuchen, es im Ursprungssignal mithilfe der Kreuzkorrelation zu finden.

1.1.5 Schneide nun ein Stück deines Signals aus und versuche es via Kreuzkorrelation im Ursprungssignal zu finden.¶

Der Prozess beginnt mit dem Ausschneiden eines Segments von einer Sekunde Länge aus dem Anfang des Originalsignals und der Visualisierung des Segments. Dieses Segment wird dann verwendet, um eine Kreuzkorrelation mit dem gesamten Originalsignal durchzuführen.

In [ ]:
# Cut 1 second from the signal
segment_length = 44100
segment = signal[:segment_length]

plot_signal(segment, t[:segment_length], title='Segment', xlabel='Time (seconds)', ylabel='Amplitude')

print("1 Sekunde des Signals")
ipd.display(ipd.Audio(data=segment, rate=44100))
1 Sekunde des Signals
Your browser does not support the audio element.
In [ ]:
plot_signals(segment, t[:segment_length], signal, title='Segment vs. Original', xlabel='Time (seconds)', ylabel='Amplitude')

Hier sehen wir das Segment zusammen mit dem Originalsignal. Das Segment ist in Rot dargestellt und das Originalsignal in Blau.

Kreuzkorrelation

In [ ]:
# Perform cross-correlation
cross_correlation = correlate(signal, segment, mode='full')
lags = np.arange(-len(segment) + 1, len(signal))

plot_cross_correlation(cross_correlation, lags, title='Kreuzkorrelation des Segments mit dem Originalsignal')

# Find the offset of the best match
offset = lags[np.argmax(cross_correlation)]
print('Offset: {} samples'.format(offset))
Offset: 0 samples

Die Grafik zeigt die Kreuzkorrelation eines ausgeschnittenen Segments mit dem Originalsignal.

Das Ergebnis eines Offsets von 0 Samples bedeutet, dass das ausgeschnittene Segment die höchste Übereinstimmung mit dem Anfang des Originalsignals aufweist, ohne dass eine Verschiebung erforderlich ist. Dies ist zu erwarten, da das Segment direkt vom Anfang des Originalsignals genommen wurde.

Das Diagramm zeigt auch, dass es neben dem Hauptpeak bei 0 Lags andere Spitzen gibt, die auf andere Stellen im Signal hinweisen, an denen das Segment ebenfalls ähnliche Muster aufweist, aber mit weniger Übereinstimmung als beim Anfang des Signals, zum Beispiel bei rund 90'000 und 115'000 Lags. Diese sekundären Peaks sind typisch für Signale mit wiederkehrenden Mustern oder sich wiederholenden Elementen.

1.1.6 Warum passt diese Stelle?¶

Die Übereinstimmung der Stelle wird, wie vorhin erklärt, durch den höchsten Peak im Kreuzkorrelationsdiagramm angezeigt. Da dieser Peak bei einem Lag von 0 Samples auftritt, bedeutet dies, dass das ausgeschnittene Segment ohne Verschiebung perfekt zum Anfang des Originalsignals passt.

1.1.7 Segment verändern & Kreuzkorrelation erneut durchführen¶

Um die Robustheit der Kreuzkorrelation zu testen, werde ich das ursprünglich ausgeschnittene Segment in drei separaten Experimenten modifizieren:

  • Experiment 1: Rauschen hinzufügen: Im ersten Experiment füge ich dem Segment eine geringe Menge Rauschen hinzu. Dies simuliert die häufig vorkommende Situation in realen Signalverarbeitungsszenarien, wo Signale durch äussere Einflüsse gestört werden können.

  • Experiment 2: Amplitude modifizieren: Im zweiten Experiment ändere ich die Amplitude des Segments, um zu prüfen, ob Änderungen in der Signalstärke die Auffindbarkeit des Segments im Originalsignal beeinflussen.

  • Experiment 3: Filterung: Im dritten Experiment wende ich einen Tiefpassfilter auf das Segment an, um zu sehen, ob die Kreuzkorrelation das Segment im Originalsignal finden kann, wenn es durch einen Filter verändert wurde.

Für alle Experimente wird dann eine Kreuzkorrelationsanalyse durchgeführt, um zu bestimmen, ob und wie genau das modifizierte Segment im Originalsignal identifiziert werden kann. Die Ergebnisse werden grafisch dargestellt, um die Positionen der grössten Übereinstimmung zu visualisieren.

1.1.7.1 Rauschen hinzufügen¶

In [ ]:
# Modify the segment by adding a small amount of noise
noise = np.random.normal(0, 0.2, segment_length)  # Noise with standard deviation of 0.2
modified_segment = segment + noise

print('Modifiziertes Signal mit Rauschen')
ipd.display(ipd.Audio(data=modified_segment, rate=44100))

plot_original_modified_signal(segment, modified_segment, title='Original und modifiziertes Signal-Segment')
Modifiziertes Signal mit Rauschen
Your browser does not support the audio element.

In diesem Diagramm überlappen das Original- und das modifizierte Signal. Trotz der Hinzufügung von Rauschen, das als eine Erhöhung der Amplitudenschwankungen und verminderte Glättungen sichtbar ist, bleibt die zugrundeliegende Form des Signals erkennbar. Dies deutet darauf hin, dass das modifizierte Segment immer noch Ähnlichkeiten mit dem Original aufweist.

Kreuzkorrelation

In [ ]:
# Perform cross-correlation with the noise segment
cross_corr_noise = correlate(signal, modified_segment, mode='full')
lags_modified = np.arange(-len(modified_segment) + 1, len(signal))

plot_cross_correlation(cross_corr_noise, lags_modified, title='Kreuzkorrelation des modifizierten Segments mit dem Originalsignal')

# Find the offset of the best match for the modified segment
offset_modified = lags_modified[np.argmax(cross_corr_noise)]
print('Offset: {} samples'.format(offset_modified))
Offset: 0 samples

In diesem Diagramm zeigt die Kreuzkorrelation des modifizierten Segments mit dem Originalsignal. Trotz der Rauschzufügung sind deutliche Spitzen an denselben Stellen wie ohne Modifizierung zu erkennen. Das heisst, die Kreuzkorrelation kann das modifizierte Segment im Originalsignal finden.

Der Vorgang zeigt, dass die Kreuzkorrelation robust genug ist, um ein Segment auch nach einer Modifikation durch Rauschen im Originalsignal zu finden.

1.1.7.2 Amplitude modifizieren¶

In [ ]:
amplified_segment = segment * 3

print('Modifiziertes Signal mit erhöhter Amplitude')
ipd.display(ipd.Audio(data=amplified_segment, rate=44100))

plot_original_modified_signal(segment, amplified_segment, title='Original und verstärktes Signal-Segment')
Modifiziertes Signal mit erhöhter Amplitude
Your browser does not support the audio element.

Kreuzkorrelation

In [ ]:
cross_corr_amplified = correlate(signal, amplified_segment, mode='full')
lags_amplified = np.arange(-len(amplified_segment) + 1, len(signal))

plot_cross_correlation(cross_corr_amplified, lags_amplified, title='Kreuzkorrelation des verstärkten Segments mit dem Originalsignal')

offset_amplified = lags_amplified[np.argmax(cross_corr_amplified)]
print('Offset: {} samples'.format(offset_amplified))
Offset: 0 samples

Der resultierende Plot der Kreuzkorrelation zeigt auf, dass das modifizierte Segment immer noch gleich im Originalsignal identifiziert werden kann. Der Lag, an dem der höchste Peak im Kreuzkorrelationsdiagramm auftritt, hat sich nicht verändert. ​​Nur die Höhe des Kreuzkorrelationswerts wurde höher, was darauf hindeutet, dass die Änderung der Amplitude die Auffindbarkeit des Segments im Originalsignal nicht beeinflusst.

1.1.7.3 Filterung¶

In [ ]:
cutoff_frequency = 200
b, a = butter_lowpass(cutoff_frequency, 44100)
filtered_segment = lfilter(b, a, segment)

print('Tiefpassgefiltertes Signal')
ipd.display(ipd.Audio(data=filtered_segment, rate=44100))

plot_original_modified_signal(segment, filtered_segment, title='Original und tiefpassgefiltertes Signal-Segment')
Tiefpassgefiltertes Signal
Your browser does not support the audio element.

Kreuzkorrelation

In [ ]:
cross_corr_filtered = correlate(signal, filtered_segment, mode='full')
lags_filtered = np.arange(-len(filtered_segment) + 1, len(signal))

plot_cross_correlation(cross_corr_filtered, lags_filtered, title='Kreuzkorrelation des gefilterten Segments mit dem Originalsignal')

offset = lags[np.argmax(cross_corr_filtered)]
print('Offset: {} samples'.format(offset))
Offset: 112803 samples

Die Kreuzkorrelation nach der Anwendung des Tiefpassfilters zeigt eine veränderte Dynamik in der Ähnlichkeitsbeziehung zwischen dem gefilterten Segment und dem Originalsignal. Der auffälligste Peak bei einem Lag von 112'803 Samples zeigt die Stelle der stärksten Übereinstimmung ausserhalb des Anfangs des Originalsignals. Dieser Lag deutet darauf hin, dass das gefilterte Segment möglicherweise eine ähnliche Struktur an einer anderen Stelle im Signal findet oder dass die Filterung Aspekte des Segments hervorgehoben hat, die vorher weniger offensichtlich waren.

Die Tatsache, dass der Kreuzkorrelationskoeffizient nach der Filterung deutlich niedriger ist (ca. 26'000 im Vergleich zu vorher ca. 80'000 und 240'000), deutet darauf hin, dass die Filterung das Signal in einer Weise verändert hat, welche die Korrelation beeinträchtigt. Dies könnte bedeuten, dass wichtige Merkmale, die zur Erkennung des Originalsegments beitragen, in den höheren Frequenzen liegen, die durch den Tiefpassfilter entfernt wurden.

1.1.8 Welche Arten von Veränderungen werden toleriert? Welche nicht?¶

Die Kreuzkorrelation ist in gewissem Masse robust gegenüber kleinen Veränderungen wie das Hinzufügen von Rauschen oder leichte Amplitudenänderungen. Diese Arten von Veränderungen verzerren das Signal, ohne seine grundlegende Struktur vollständig zu verändern und werden noch erkannt, wie meine Experimente gezeigt haben.

Allerdings wird die Kreuzkorrelation empfindlicher auf Veränderungen reagieren, die die grundlegenden Frequenzkomponenten des Signals beeinflussen. Ein Tiefpassfilter, der signifikante Frequenzanteile des Signals entfernt, verändert das Signal so sehr, dass es möglicherweise nicht mehr genau an seiner ursprünglichen Position im Originalsignal identifiziert werden kann, was durch die Reduktion des Kreuzkorrelationskoeffizienten und das Auftreten eines Maximums bei einem anderen Lag als 0 im Experiment deutlich wurde. Solche Veränderungen, die die charakteristischen Merkmale des Signals beeinträchtigen, werden nicht gut toleriert und können die Fähigkeit der Kreuzkorrelation, das Segment korrekt zu lokalisieren, stark beeinträchtigen.

1.1.9 Diskutiere die Resultate in ca. 150 Wörtern.¶

In den durchgeführten Experimenten zur Kreuzkorrelation wurden drei verschiedene Veränderungen an einem Signalsegment vorgenommen: das Hinzufügen von Rauschen, die Änderung der Amplitude und die Anwendung eines Tiefpassfilters. Die ersten beiden Veränderungen hatten geringe Auswirkungen auf die Identifizierbarkeit des Segments im Originalsignal, wobei die Kreuzkorrelationskoeffizienten hoch blieben und die Peaks nahe bei Lag 0 lagen. Dies zeigt, dass die Kreuzkorrelation gegenüber solchen kleinen Störungen robust ist. Das dritte Experiment mit dem Tiefpassfilter jedoch führte zu einer erheblichen Reduktion des Kreuzkorrelationskoeffizienten und verschob den Hauptpeak von Lag 0 zu einem anderen Wert. Dies verdeutlicht, dass signifikante Änderungen im Frequenzspektrum des Signals – insbesondere solche, die wesentliche Komponenten entfernen – die Auffindbarkeit des Segments im Originalsignal beeinträchtigen. Insgesamt illustrieren die Resultate die Stärken und Grenzen der Kreuzkorrelation als Methode zur Signalanalyse und -erkennung.

1.2. Segmentierung, morphologische Operationen, Objekteigenschaften in Bildern¶

Suche 1 Bild, welches mehrere ähnliche Objekte enthält. Diese Objekte sollen mittels geeigneter Methoden segmentiert werden. Die Resultate sollen als gelabelte Bilder (binär oder pro Klasse 1 Label) gespeichert werden. Zeige dabei, wie du die Labelmasken mittels morphologischer Operationen verbessert hast. Erkläre hier für jede angewendete Operation in 1-2 Sätzen, warum du diese Operation anwendest. Zeige auch in Einzelbildern die Zwischenresultate deiner angewendeten Operationen und dass du nur minimal die Objekte verändert hast (z.B. keine Verschiebungen, Verkleinerungen oder Vergrösserungen). Extrahiere am Ende deine einzelnen Objekte, zähle und vermesse 2-3 Eigenschaften deiner extrahierten Objekte mittels geeigneten Methoden. Erkläre pro Eigenschaft in 1-2 Sätzen, warum du diese gewählt hast und ob die Resultate brauchbar sind.

Erstelle dann ein möglichst minimales aber repräsentatives Skeleton eines deiner Objekte und gebe die Anzahl Pixel des Skeletons aus.

Diskutiere deine Erkenntnisse und Resultate in ca. 150 Wörtern.

Weiterführende Links:

  • skimage: Label image regions
  • skimage: Segment human cells (in mitosis)
  • skimage: Measure region properties

1.2.1 Bild auswählen¶

Bild von mactechnews

In [ ]:
image = cv2.imread("Bilder/verkehrschild.png")
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

plot_image(image_rgb, title='Original RGB Image', cmap=None)

1.2.2 Segmentierung der Objekte & Maskierung¶

Hier werde ich kurz die Schilder anhand der Farbe segmentieren. Danach werde ich die Schilder mit einer Maske aus dem Bild extrahieren.

Ich werde in diesem Abschnitt auch eine Maskierung erstellen, um die roten Bereiche im Bild mit weiss zu ersetzen, damit die Schilder besser erkannbar sind und die ganze Segmentierung einafcher wird.

In [ ]:
# Red range
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([179, 255, 255])

# Blue range
lower_blue = np.array([100, 150, 50])
upper_blue = np.array([140, 255, 255])

# White range
lower_white = np.array([0, 0, 252])
upper_white = np.array([172, 111, 255])

mask_red1 = cv2.inRange(image_hsv, lower_red1, upper_red1)
mask_red2 = cv2.inRange(image_hsv, lower_red2, upper_red2)
mask_red = cv2.add(mask_red1, mask_red2)

mask_blue = cv2.inRange(image_hsv, lower_blue, upper_blue)
mask_white = cv2.inRange(image_hsv, lower_white, upper_white)

mask = cv2.add(cv2.add(mask_blue, mask_red), mask_white)

# Combine red mask
red_mask = mask_red1 | mask_red2

# Apply red mask to original image
objects = cv2.bitwise_and(image_rgb, image_rgb, mask=mask)

plot_image_list(
    img_list=[image_rgb, objects, red_mask],  
    titles=['Original Image', 'Segmented Objects', 'Red Mask'],
    cmap='gray'
)

Hier sehen wir das Originalbild, die extrahierten Verkehrsschilder und auch die isolierten rote Bereiche (Maskierung der roten Farbe). Ich werde diese Maske nun mit dem originalen Graustufenbild kombinieren und die weitere Segmentierung mit dem kombinierten Bild durchführen.

In [ ]:
segmented_red = np.where(red_mask, 255, image_grey)

plot_image(segmented_red, title='Grey Image with Masked Red Areas', cmap='gray')

Die Verfeinerung der Maskierung für die roten Bereiche hat zu einer klareren Darstellung der Verkehrsschilder geführt. Ich werde nun mit diesem neuen Graustufenbild weiterfahren.

In [ ]:
image_grey = segmented_red

1.2.2 Thresholding¶

scikit-image hat einige Filter-Methoden, das Bild zu binarisieren. Ich habe hier drei davon auf mein Bild angewendet; Mean, Otsu und Triangle.

In [ ]:
thresh_mean = round(filters.threshold_mean(image_grey),3)
thresh_otsu = round(filters.threshold_otsu(image_grey),3)
thresh_triangle = round(filters.threshold_triangle(image_grey),3)

plot_image_list(
    img_list=[image_grey > thresh_mean, image_grey > thresh_otsu, image_grey > thresh_triangle],
    titles=[
        'Binary Image \nThreshold Mean ({})'.format(thresh_mean),
        'Binary Image \nThreshold Otsu ({})'.format(thresh_otsu),
        'Binary Image \nThreshold Triangle ({})'.format(thresh_triangle)],
    cmap='gray'
)

Im Vergleich scheint die Triangle-Threshold in diesem Fall die beste der drei Thresholds zu liefern, da sie alles im Hintergrund entfernt hat und gleichzeitig nicht zu viele Details der Schilder vermindert hat. Es könnte jedoch sein, dass die Triangle-Methode etwas zu streng ist, wodurch die Gefahr besteht, dass zu viele Details verloren gehen.

Schauen wir die Labels an, um eine bessere Vorstellung davon zu bekommen, wie die einzelnen Thresholds funktionieren.

In [ ]:
label_image_mean = measure.label(image_grey > thresh_mean)
label_image_otsu = measure.label(image_grey > thresh_otsu)
label_image_triangle = measure.label(image_grey > thresh_triangle)

plot_image_list(
    img_list=[label_image_mean, label_image_otsu, label_image_triangle],
    titles=[
        'Labeled Image \nThreshold Mean \nAmount of objects found: {}'.format(len(measure.regionprops(label_image_mean))),
        'Labeled Image \nThreshold Otsu \nAmount of objects found: {}'.format(len(measure.regionprops(label_image_otsu))),
        'Labeled Image \nThreshold Triangle \nAmount of objects found: {}'.format(len(measure.regionprops(label_image_triangle)))],
    cmap=plt.cm.nipy_spectral
)

Die Segmentierungsergebnisse der Mean-, der Otsu- und der Triangle-Methode deuten auf eine Übersegmentierung hin, was sich in der übermässig hohen Anzahl von Labels in jedem Fall zeigt. Die Methode des Mean-Thresholds ergab 1364 Objekte, die Otsu-Methode identifizierte 1302 Objekte und die Triangle-Methode fand 206 Objekte. Diese Zahlen deuten darauf hin, dass jede Methode nicht nur die Verkehrsschilder erfasst, die die Objekte von Interesse sind, sondern auch viele irrelevante Merkmale oder Rauschen, was zu einer Vielzahl von unnötigen und fremden Markierungen führt.

Bevor ich mit dem Fine-Tuning (Bereinigung) der Segmentierung fortfahre, möchte ich einige manuelle Thresholds ausprobieren. Ich finde, dass ein Threshold zwischen 170 und 220 die beste Segmentierung liefern könnte.

In [ ]:
plot_image_list(
    img_list=[image_grey > 170, image_grey > 190, image_grey > 220],
    titles=['Binary Image \nThreshold > 170', 'Binary Image \nThreshold > 190', 'Binary Image \nThreshold > 220'],
    cmap='gray'
)
  • Threshold > 170: Beim Schwellenwert von 170 wurde ein Teil des Hintergrundrauschens entfernt, aber es ist immer noch eine grosse Menge vorhanden. Einige Details der Schilder werden besser hervorgehoben, aber das Bild ist nicht sauber genug für eine präzise Segmentierung.

  • Threshold > 190: Bei diesem Schwellenwert wird das Hintergrundrauschen weiter reduziert, aber es ist immer noch vorhanden.

  • Threshold > 220: Dieser höhere Schwellenwert liefert die sauberste der drei binären Masken. Die Zeichen sind gut definiert und der Grossteil des Hintergrundrauschens ist eliminiert. Die Grenzen der Zeichen sind klar, was für eine genaue Segmentierung und anschliessende Analyse entscheidend ist.

In [ ]:
label_image_170 = measure.label(image_grey > 170)
label_image_190 = measure.label(image_grey > 190)
label_image_220 = measure.label(image_grey > 220)

plot_image_list(
    img_list=[label_image_170, label_image_190, label_image_220],
    titles=[
        'Labeled Image \nThreshold > 170 \nAmount of objects found: {}'.format(len(measure.regionprops(label_image_170))),
        'Labeled Image \nThreshold > 190 \nAmount of objects found: {}'.format(len(measure.regionprops(label_image_190))),
        'Labeled Image \nThreshold > 220 \nAmount of objects found: {}'.format(len(measure.regionprops(label_image_220)))],
    cmap=plt.cm.nipy_spectral
)

Die Segmentierungsergebnisse mit Thresholds von 170 und 190 deuten auf eine Übersegmentierung hin, was sich in der übermässig hohen Anzahl von Labels in jedem Fall zeigt. Die Methode mit Threshold 220 fand 78 Objekte, was eine erhebliche Verbesserung darstellt, aber immer noch zu viele Labels sind.

Um dieser Übersegmentierung entgegenzuwirken, wird der nächste Schritt darin bestehen, die Segmentierung durch morphologische Operationen zu verfeinern. Diese Operationen, zu denen Prozesse wie Erosion, Dilatation, Öffnen und Schliessen gehören, können helfen, Rauschen und kleine irrelevante Objekte zu entfernen und Löcher innerhalb der Objekte zu füllen. Dies führt zu einer saubereren und genaueren Segmentierung. Durch die Feinabstimmung dieser morphologischen Operationen möchte ich die Qualität der Segmentierung so verbessern, dass das resultierende beschriftete Bild die tatsächliche Anzahl relevanter Objekte im Bild besser wiedergibt.

Für die weiteren Schritte werde ich mit dem Threshold von 220 weiterzumachen.

In [ ]:
binary_mask = image_grey > 220

1.2.3 Morphologische Operationen¶

Ziel ist es hier, die Anzahl der Labels zu reduzieren, ohne die Objekte gross zu verändern. Wir wollen möglichst wenige Löcher und Rauschen haben. Aber es ist auch wichtig, dass wir nicht zu viele Details verlieren. Wenn wir zum Beispiel ein starkes Closing anwenden, würden die Pfeile in den Schildern verschwinden.

In [ ]:
# Remove small objects/noise
cleaned_mask = morphology.remove_small_objects(binary_mask, min_size=150)

# Perform closing to fill small holes inside the objects
closed_mask = morphology.binary_closing(cleaned_mask, morphology.disk(5))

plot_image_list(
    img_list=[binary_mask, cleaned_mask, closed_mask],
    titles=['Binary Mask', 'Cleaned Mask', 'Closed Mask'],
    cmap='gray'
)

Aus der Visualisierung der Operationen können wir die folgenden Beobachtungen und Interpretationen ableiten:

  1. Cleaned Mask: Dieses Bild zeigt die Objekte nach Entfernung von kleinen Objekten und Rauschen.

  2. Closed Mask: Die "Closing"-Operation hat dazu geführt, dass jegliche kleinen Löcher innerhalb der Objekte aufgefüllt wurden, zum Beispiel die Texte und die Ränder der Schilder. Dies führt zu grösseren, einheitlichen Formen, die für die Segmentierung besser geeignet sind.

1.2.4 Labelling¶

Nun können wir die endgültige Beschriftung (Labelling) durchführen. Wir werden die Labels der "Closed Mask" verwenden, da sie die sauberste und genaueste der drei binären Masken ist.

In [ ]:
label_image = measure.label(closed_mask)

# Measure properties of labeled regions
properties = measure.regionprops(label_image)

print('Anzahl der gefundenen Objekte: {}'.format(len(properties)))

plot_image(label_image, "Labeled image", plt.cm.nipy_spectral)
Anzahl der gefundenen Objekte: 5

Das bereinigte Binärbild wurde nun beschriftet, d.h. jedes einzelne Objekt im Bild wurde erkannt und mit einer eindeutigen Bezeichnung versehen. Die Anzahl der Labels ist auf 5 gesunken, was eine erhebliche Verbesserung gegenüber den ursprünglichen 78 Labels darstellt. Die Labels sind auch klarer und besser definiert, was für die Analyse von entscheidender Bedeutung ist.

1.2.5 Objekteigenschaften extrahieren¶

Nun werde ich folgende Eigenschaften der ersten vier erkannten Objekts messen:

  • Fläche: Die Anzahl der Pixel innerhalb des Objekts, eine direkte Messung der Grösse des Objekts im Bildraum.

  • Exzentrizität: Ein Mass für die Abweichung der Form des Objekts von einer perfekten Kreisform; eine Exzentrizität von 0 entspricht einem Kreis, während Werte näher an 1 längliche Formen anzeigen.

  • Ausrichtung: Der Winkel zwischen der Hauptachse der besten passenden Ellipse des Objekts und der horizontalen Achse des Bildes, der die Ausrichtung der Objektform beschreibt.

  • Schwerpunkt: Die Koordinaten des Schwerpunkts des Objekts, die den geometrischen Mittelpunkt des Objekts im Bild darstellen. Diese Eigenschaft ist wichtig, da sie die Position des Objekts im Bildraum beschreibt.

In [ ]:
# Extract the properties we are interested in
properties_to_extract = ('area', 'eccentricity', 'orientation', 'centroid')
extracted_properties = [{prop: getattr(region, prop) for prop in properties_to_extract} 
                        for region in properties]

for i, region in enumerate(extracted_properties):
    print('Objekt {}:'.format(i + 1))
    print('Fläche: {} px'.format(region['area']))
    print('Exzentrizität: {}'.format(region['eccentricity']))
    print('Orientierung: {}°'.format(np.rad2deg(region['orientation'])))
    print('Schwerpunkt: {}'.format(region['centroid']))
    print()
Objekt 1:
Fläche: 83510.0 px
Exzentrizität: 0.8009577491590977
Orientierung: -2.1353716179241222°
Schwerpunkt: (325.40322117111725, 847.0769847922404)

Objekt 2:
Fläche: 31607.0 px
Exzentrizität: 0.3865014553007669
Orientierung: -48.820061811153096°
Schwerpunkt: (240.96782358338342, 487.3686525136837)

Objekt 3:
Fläche: 31716.0 px
Exzentrizität: 0.8407817105641066
Orientierung: -85.9298060485047°
Schwerpunkt: (460.736599823433, 483.46985748518097)

Objekt 4:
Fläche: 86573.0 px
Exzentrizität: 0.8086680333941312
Orientierung: -1.632339827170025°
Schwerpunkt: (855.8576114955009, 847.138934771811)

Objekt 5:
Fläche: 60944.0 px
Exzentrizität: 0.789751142521551
Orientierung: -3.0652403806841177°
Schwerpunkt: (858.7866073772643, 484.27093725387243)

Interpretation der Ergebnisse¶

Diese Eigenschaften geben Aufschluss über die Form, Ausrichtung und Position der Objekte im Bild. Hier ist eine Interpretation jeder Eigenschaft:

  • Fläche: Die Fläche der Objekte variiert erheblich, was auf unterschiedlich grosse Objekte im Bild hinweist. Objekt 1 und 4 haben eine deutlich grössere Fläche als Objekt 2, 3 und 5, was darauf schliessen lässt, dass sie im Bild prominenter sind.

  • Exzentrizität: Die Exzentrizitäten von Objekt 1, 3, 4 und 5 sind nahe an 1, was auf längliche Formen hinweist, während Objekt 2 mit einer geringeren Exzentrizität näher an der Kreisform ist. Diese Masszahl ist sinnvoll, um zwischen eher runden und gestreckten Objekten zu unterscheiden.

  • Ausrichtung: Die Ausrichtung gibt die Neigung der Objekte an. Objekt 2 hat eine starke Neigung (circa -49°), was auf eine deutliche Abweichung von der vertikalen oder horizontalen Ausrichtung hinweist. Die Ausrichtungen von Objekt 1, 4 und 5 sind nahezu horizontal, während die von Objekt 3 fast vertikal ist (circa -86°), was auf eine aufrechte Positionierung hindeutet.

  • Schwerpunkt: Die Schwerpunkte geben die zentrale Lage der Objekte im Bild an. Objekte 4 und 5 haben ähnliche X-Koordinaten, aber verschiedene Y-Koordinaten, was darauf hindeutet, dass sie in der gleichen vertikalen Ebene, aber auf unterschiedlichen Höhen im Bild liegen. Objekt 2 und 3 haben ähnliche Y-Koordinaten, was darauf hindeutet, dass sie auf der gleichen horizontalen Ebene liegen.

Die gemessenen Eigenschaften sind sinnvoll für die Charakterisierung und Unterscheidung der Objekte in einem Bild und können zur Analyse von Form und Position genutzt werden.

1.2.6 Skeletonisierung¶

Für die weitere Analyse werde ich ein Skelett der Objekte erstellen. Das Skelett ist eine dünne Version der Form, die sich in gleichem Abstand zu ihren Grenzen befindet. Es bietet eine vereinfachte Darstellung der Form.

In [ ]:
skeleton = morphology.skeletonize(closed_mask)

print('Anzahl der Pixel im Bild: {}'.format(label_image.size))
print('Anzahl der Pixel im Skelett: {}'.format(np.sum(skeleton)))

plot_image(skeleton, "Skeletons of labelled Objects", cmap='gray')
Anzahl der Pixel im Bild: 1630244
Anzahl der Pixel im Skelett: 8407

Interpretation der Ergebnisse¶

  1. Strukturerhaltung: Jedes Skelett behält die grundlegende Form und Orientierung des ursprünglichen Objekts bei. Es ist vor allem interessant zu sehen, wie sich die Pfeile in den Schilder verändert haben. Das Skelett ermöglicht es, die Formeigenschaften der Objekte mit weniger Pixeln zu analysieren.

  2. Topologische Analyse: Skelette können genutzt werden, um die topologischen Eigenschaften der Objekte wie Konnektivität, Anzahl der Löcher oder Verzweigungen zu verstehen. Dies ist nützlich in Bildverarbeitungsanwendungen wie Mustererkennung und Objektklassifikation.

  3. Komplexitätsreduktion: Mit nur 8407 Pixeln im Skelett im Vergleich zur gesamten Pixelanzahl der Objekte (1'630'244) bietet es eine sehr kompakte Repräsentation, die für die Analyse und Verarbeitung effizienter ist.

  4. Feature-Extraktion: Skelette können für die Feature-Extraktion verwendet werden, um wichtige Punkte wie Endpunkte oder Verzweigungspunkte zu identifizieren, die für weiterführende Analysen wie die Objekterkennung nützlich sind.

Die Skelettierung ist besonders wertvoll, wenn es darum geht, die strukturellen und geometrischen Eigenschaften der Objekte in einem Bild zu analysieren und zu verstehen, ohne durch die Breite der Objekte abgelenkt zu werden. Sie zeigt auch, wie gut die Objekte durch linienhafte Strukturen repräsentiert werden können.

2. Feature Deskriptoren in Bildern (LE4)¶

2.1. Keypoint Matching¶

Suche ein paar Bilder mit dem gleichen Sujet/Objekt aus, welche das Objekt von unterschiedlichen Blickwinkeln, aus anderer Perspektive, aus unterschiedlicher Distanz oder rotiert zeigen. Wende dann den dir zugeordneten Keypoint Deskriptor {'ORB'} an, um mindestens zwei deiner Bilder via Keypoints zu "matchen". Wähle dafür geeignete Parameter und eine geeignete Anzahl Keypoints. Erläutere deine Entscheidungen in 1-2 Sätzen. Zeige deine Resultate visuell und stelle 2-3 Beobachtungen auf. Diskutiere in ca. 150 Wörtern wie robust der dir zugeordnete Algorithms {'ORB'} ist in Bezug auf Transformationen oder unterschiedlicher Aufnahmeverhältnisse (Licht, ...) und dessen rechnerische Effizienz. Zeige mindestens eine dieser Eigenschaften anhand deiner Beispieldaten. Diskutiere die Resultate und Erkenntnisse in 2-3 Sätzen.

In [ ]:
images_rgb, images_grey, images_hsv = read_images("mosalah")

plot_image_list(images_rgb, titles=[None, None, None, None])

# resize all images_grey
images_grey = [cv2.resize(image, (624, 351)) for image in images_grey]

Bilder von BBC.com

2.1.1. Keypoint-Detektion mit ORB¶

Der ORB (Oriented FAST and Rotated BRIEF) Algorithmus verwendet den FAST-Algorithmus zur Berechnung der Keypoints. FAST steht für "Features from Accelerated Segments Test". FAST berechnet die Keypoints unter Berücksichtigung der Pixelhelligkeit um einen bestimmten Bereich.

  • Medium - Understanding Keypoints and ORB algorithm

Bestimmung der Anzahl Keypoints

Für die Bestimmung der Anzahl Keypoints beachte ich folgende Faktoren:

  • Bildinhalt: Wenn die Bilder reiche Texturen und viele unterschiedliche Merkmale aufweisen, sollte man mehr Keypoints verwenden, um genügend der verschiedenen Elemente zu erfassen. Bei einfacheren Bildern mit weniger einzigartigen Merkmalen kann man hingegen mit weniger Keypoints auskommen. In meinem Fall handelt es sich um ein detailreiches Bild.

  • Anforderungen an die Aufgabe: Je nach der spezifischen Aufgabe kann man entweder eine hohe Auffindbarkeit (das Finden möglichst vieler Übereinstimmungen, was nützlich für Rekonstruktionsaufgaben ist) oder eine hohe Präzision (das Sicherstellen, dass die Übereinstimmungen korrekt sind, was für die Objekterkennung wichtig ist) benötigen. Mehr Keypoints können die Auffindbarkeit verbessern, aber gleichzeitig die Präzision verringern. Es ist wichtig, die Anforderungen der jeweiligen Aufgabe im Auge zu behalten.

Bestimmung der Parameter

  • fast_threshold=0.05: Schwellenwert zur Erkennung von Eckpunkten. Ich habe einen tiefen Wert gewählt aufgrund folgender Gründe: Mehr erkannte Eckpunkte, erhöhte Empfindlichkeit und höhere Informationsdichte.

  • Weitere Parameter sind hier zu finden: GitHub - orb.py

In [ ]:
from skimage.feature import ORB

n_keypoints = 300

orb = ORB(n_keypoints=n_keypoints, fast_threshold=0.05)

keypoints_list, descriptors_list = get_keypoints_and_descriptors(images_grey, orb)

plot_keypoints(images_grey, keypoints_list)

Die Bilder zeigen Keypoints, die vom ORB-Feature-Detektor auf vier verschiedene Bilder des gleichen Fussballspielers identifiziert wurden. Diese Punkte repräsentieren charakteristische Merkmale in den Bildern, die für die nächste Aufgabe verwendet werden können, um die Bilder zu vergleichen.

Auf den Bildern konzentrieren sich die Keypoints auf folgende Bereiche:

  • Gesicht und Haare: Das Gesicht und die Haare des Spielers sind deutlich erkennbare Merkmale, die von den Algorithmen aufgrund ihrer Textur und Kanten häufig als Keypoints gewählt werden.

  • Trikot und Werbung: Logos, Text und Grafiken auf dem Trikot sind ebenfalls kontrastreich und bieten gute Anhaltspunkte für die Keypoints-Detektion.

  • Arme und Hände: Die Konturen der Arme und Hände, besonders gegen einen kontrastierenden Hintergrund, werden oft hervorgehoben.

2.1.2. Keypoint-Matching mit ORB¶

Nun werde ich die Keypoints auf den vier verschiedenen Bildern vergleichen, um festzustellen, ob sie sich auf ähnliche Bereiche konzentrieren. Dies ist ein erster Schritt, um festzustellen, wie ähnlich die Bilder sind.

In [ ]:
match_and_visualize(images_grey, keypoints_list, descriptors_list, n_keypoints)

Die Keypoint-Matching-Ergebnisse zeigen, dass die Anzahl der Übereinstimmungen zwischen verschiedenen Bildern variiert. Dies liegt an der unterschiedlichen Sichtbarkeit und Position von charakteristischen Merkmalen auf den Bildern.

Bilder 1 und 4, die ähnliche Ansichten des Objekts zeigen, haben wahrscheinlich aufgrund fehlender "Standard Chartered" Logos im Bild 4 nicht die höchste Anzahl an Übereinstimmungen, obwohl sie visuell am ähnlichsten erscheinen. Dies deutet darauf hin, dass die Anzahl der detektierten Keypoints einen wesentlichen Einfluss auf die Ergebnisse des Matchings hat.

Ich werde nun die Anzahl der Keypoints auf den Bildern erhöhen, um zu sehen, wie stark dies die Anzahl der Übereinstimmungen der jeweiligen Bilder verändert.

Erhebliche Erhöhung der Anzahl Keypoints

In [ ]:
n_keypoints = 1000

orb = ORB(n_keypoints=n_keypoints, fast_threshold=0.05)

keypoints_list, descriptors_list = get_keypoints_and_descriptors(images_grey, orb)

match_and_visualize(images_grey, keypoints_list, descriptors_list, n_keypoints)

Wenn man aber die Anzahl der Keypoints auf 1000 erhöht, werden bei Bild 1 und 4 am meisten Übereinstimmungen gefunden, da mehr Merkmale (vorallem im Gesicht und Hände) zur Verfügung stehen. Man muss aber beachten, dass im die Genauigkeit der Matches bei einigen Vergleiche stark gesunken sind (z.B Bild 1 und 2). Dies zeigt wiederum wie wichtig es ist, die Parameter gut zu untersuchen und Experimente durchzuführen. Es kann bei so vielen Keypoints aber sein, dass sie zufälligerweise übereinstimmen, obwohl sie nicht wirklich gleich sind.

2.1.3 Robustheit unterschiedlicher Aufnahmeverhältnisse¶

Ich werde in diesem Experiment die Bilder 1 und 2 verwenden, wobei Bild 2 eine erhebliche Verdunkelung aufweist. Dieses Experiment wird dazu dienen, die Robustheit der Algorithmus auf unterschiedliche Lichtverhältnisse zu testen.

In [ ]:
from skimage import exposure

# Darken image 2 by applying gamma correction
images_grey_darkened = exposure.adjust_gamma(images_grey[1], 5)

new_image_list = [images_grey[0], images_grey_darkened]

plot_image(images_grey_darkened, "Image 2 Verdunkelt", cmap='gray')

300 Keypoints

In [ ]:
n_keypoints = 300

orb = ORB(n_keypoints=n_keypoints, fast_threshold=0.05)

keypoints_list, descriptors_list = get_keypoints_and_descriptors(new_image_list, orb)

match_and_visualize(new_image_list, keypoints_list, descriptors_list, n_keypoints)

Vor der Verdunkelung von Bild 2 hatten Bild 1 und 2 mit je 300 Keypoints 96 Matches, was eine Übereinstimmung von 32% entspricht. Nachdem das Bild 2 stark verdunkelt wurde, sinkt die Anzahl der Übereinstimmungen erheblich auf nur 67, was einer Übereinstimmung von 22.3% entspricht.

1000 Keypoints

In [ ]:
n_keypoints = 1000

orb = ORB(n_keypoints=n_keypoints, fast_threshold=0.05)

keypoints_list, descriptors_list = get_keypoints_and_descriptors(new_image_list, orb)

match_and_visualize(new_image_list, keypoints_list, descriptors_list, n_keypoints)

Vor der Verdunkelung und mit je 1000 Keypoints entsprach die Übereinstimmung 22.6%. Nach der Verdunkelung sinkt die Übereinstimmung auf 21.6%. Dies ist eine viel kleinere Abnahme als bei 300 Keypoints.

  • Der ORB-Algorithmus kann also empfindlich gegenüber Helligkeitsänderungen in den Bildern sein. Es hängt jedoch ab, wie man die Parameter setzt. Wenn Bild 2 zu stark verdunkelt wird, können die ursprünglichen Keypoints eventuell nicht mehr richtig erkannt werden, und es kann schwierig sein, Übereinstimmungen zu finden. Mit 300 Keypoints sind die verfügbaren charakteristischen Merkmale begrenzter, daher ist die Abnahme der Übereinstimmungen spürbar.

  • Mit 1000 Keypoints gibt es mehr charakteristische Merkmale, die erkannt werden können, und diese Merkmale sind möglicherweise besser in der Lage, Helligkeitsänderungen zu überwinden. Dies kann dazu führen, dass der ORB-Algorithmus nach der Verdunkelung immer noch eine ausreichende Anzahl von Übereinstimmungen findet.

  • Wenn die Verdunkelung von Bild 2 extrem ist, kann es schwieriger sein, Übereinstimmungen zu finden, unabhängig von der Anzahl der Keypoints. Wenn das Bild zu stark verdunkelt ist, gehen möglicherweise wichtige Merkmale und Informationen verloren, was die Übereinstimmung beeinträchtigt.

  • Wie vorhin erwähnt, besteht mit 1000 Keypoints eine grössere Wahrscheinlichkeit, dass zufällige Übereinstimmungen auftreten.

Abschliessend möchte ich die Genauigkeiten der Keypoint-Matches für verschiedene Anzahl von Keypoints vergleichen und auf einem Blick veranschaulichen:

Anzahl Keypoints Bild 1 - Bild 2 (Matches) Bild 1 - Bild 3 (Matches) Bild 1 - Bild 4 (Matches) Bild 2 - Bild 3 (Matches) Bild 2 - Bild 4 (Matches) Bild 3 - Bild 4 (Matches) Durchschnittliche Genauigkeit
25 13 11 7 9 12 10 0.4133
50 25 19 12 19 16 17 0.36
100 40 30 25 30 32 31 0.3133
200 68 41 36 61 57 54 0.2642
300 96 61 65 77 84 70 0.2517
500 122 99 104 127 129 108 0.2297
750 171 166 158 188 168 177 0.2285
1000 226 222 236 227 209 228 0.2247

Es ist gut zu sehen, dass je mehr Keypoints gewählt werden, desto mehr sinkt die Genauigkeit der Übereinstimmungen. Es hängt jedoch auch davon ab, was man genau erreichen möchte. Möchte man möglichst viele Übereinstimmungen? Oder möchte man eine möglichst hohe Genauigkeit? Es hängt also vieles vom Use-Case ab.

In [ ]:
keypoints_to_test = [25, 50, 100, 200, 300, 500, 750, 1000]

for n_keypoints in keypoints_to_test:
    orb = ORB(n_keypoints=n_keypoints, fast_threshold=0.05)
    keypoints_list, descriptors_list = get_keypoints_and_descriptors(images_grey, orb)
    match_and_visualize(images_grey, keypoints_list, descriptors_list, n_keypoints, plot=False)
Bild 1 - Bild 2 (13 Matches für 25 Keypoints) - (0.52 Genauigkeit)
Bild 1 - Bild 3 (11 Matches für 25 Keypoints) - (0.44 Genauigkeit)
Bild 1 - Bild 4 (7 Matches für 25 Keypoints) - (0.28 Genauigkeit)
Bild 2 - Bild 3 (9 Matches für 25 Keypoints) - (0.36 Genauigkeit)
Bild 2 - Bild 4 (12 Matches für 25 Keypoints) - (0.48 Genauigkeit)
Bild 3 - Bild 4 (10 Matches für 25 Keypoints) - (0.4 Genauigkeit)
Durchschnittliche Genauigkeit: 0.4133
Bild 1 - Bild 2 (25 Matches für 50 Keypoints) - (0.5 Genauigkeit)
Bild 1 - Bild 3 (19 Matches für 50 Keypoints) - (0.38 Genauigkeit)
Bild 1 - Bild 4 (12 Matches für 50 Keypoints) - (0.24 Genauigkeit)
Bild 2 - Bild 3 (19 Matches für 50 Keypoints) - (0.38 Genauigkeit)
Bild 2 - Bild 4 (16 Matches für 50 Keypoints) - (0.32 Genauigkeit)
Bild 3 - Bild 4 (17 Matches für 50 Keypoints) - (0.34 Genauigkeit)
Durchschnittliche Genauigkeit: 0.36
Bild 1 - Bild 2 (40 Matches für 100 Keypoints) - (0.4 Genauigkeit)
Bild 1 - Bild 3 (30 Matches für 100 Keypoints) - (0.3 Genauigkeit)
Bild 1 - Bild 4 (25 Matches für 100 Keypoints) - (0.25 Genauigkeit)
Bild 2 - Bild 3 (30 Matches für 100 Keypoints) - (0.3 Genauigkeit)
Bild 2 - Bild 4 (32 Matches für 100 Keypoints) - (0.32 Genauigkeit)
Bild 3 - Bild 4 (31 Matches für 100 Keypoints) - (0.31 Genauigkeit)
Durchschnittliche Genauigkeit: 0.3133
Bild 1 - Bild 2 (68 Matches für 200 Keypoints) - (0.34 Genauigkeit)
Bild 1 - Bild 3 (41 Matches für 200 Keypoints) - (0.205 Genauigkeit)
Bild 1 - Bild 4 (36 Matches für 200 Keypoints) - (0.18 Genauigkeit)
Bild 2 - Bild 3 (61 Matches für 200 Keypoints) - (0.305 Genauigkeit)
Bild 2 - Bild 4 (57 Matches für 200 Keypoints) - (0.285 Genauigkeit)
Bild 3 - Bild 4 (54 Matches für 200 Keypoints) - (0.27 Genauigkeit)
Durchschnittliche Genauigkeit: 0.2642
Bild 1 - Bild 2 (96 Matches für 300 Keypoints) - (0.32 Genauigkeit)
Bild 1 - Bild 3 (61 Matches für 300 Keypoints) - (0.2033 Genauigkeit)
Bild 1 - Bild 4 (65 Matches für 300 Keypoints) - (0.2167 Genauigkeit)
Bild 2 - Bild 3 (77 Matches für 300 Keypoints) - (0.2567 Genauigkeit)
Bild 2 - Bild 4 (84 Matches für 300 Keypoints) - (0.28 Genauigkeit)
Bild 3 - Bild 4 (70 Matches für 300 Keypoints) - (0.2333 Genauigkeit)
Durchschnittliche Genauigkeit: 0.2517
Bild 1 - Bild 2 (122 Matches für 500 Keypoints) - (0.244 Genauigkeit)
Bild 1 - Bild 3 (99 Matches für 500 Keypoints) - (0.198 Genauigkeit)
Bild 1 - Bild 4 (104 Matches für 500 Keypoints) - (0.208 Genauigkeit)
Bild 2 - Bild 3 (127 Matches für 500 Keypoints) - (0.254 Genauigkeit)
Bild 2 - Bild 4 (129 Matches für 500 Keypoints) - (0.258 Genauigkeit)
Bild 3 - Bild 4 (108 Matches für 500 Keypoints) - (0.216 Genauigkeit)
Durchschnittliche Genauigkeit: 0.2297
Bild 1 - Bild 2 (171 Matches für 750 Keypoints) - (0.228 Genauigkeit)
Bild 1 - Bild 3 (166 Matches für 750 Keypoints) - (0.2213 Genauigkeit)
Bild 1 - Bild 4 (158 Matches für 750 Keypoints) - (0.2107 Genauigkeit)
Bild 2 - Bild 3 (188 Matches für 750 Keypoints) - (0.2507 Genauigkeit)
Bild 2 - Bild 4 (168 Matches für 750 Keypoints) - (0.224 Genauigkeit)
Bild 3 - Bild 4 (177 Matches für 750 Keypoints) - (0.236 Genauigkeit)
Durchschnittliche Genauigkeit: 0.2285
Bild 1 - Bild 2 (226 Matches für 1000 Keypoints) - (0.226 Genauigkeit)
Bild 1 - Bild 3 (222 Matches für 1000 Keypoints) - (0.222 Genauigkeit)
Bild 1 - Bild 4 (236 Matches für 1000 Keypoints) - (0.236 Genauigkeit)
Bild 2 - Bild 3 (227 Matches für 1000 Keypoints) - (0.227 Genauigkeit)
Bild 2 - Bild 4 (209 Matches für 1000 Keypoints) - (0.209 Genauigkeit)
Bild 3 - Bild 4 (228 Matches für 1000 Keypoints) - (0.228 Genauigkeit)
Durchschnittliche Genauigkeit: 0.2247